
local ffi = require('ffi');
local band = bit.band;
local rshift = bit.rshift;
local lshift = bit.lshift;
local bxor = bit.bxor
local bor = bit.bor;
local print = function()end;
local function BIT(v,idx)
    return band(rshift(v,idx),1);
end

function bitswap(v,n,...)
    local args = {...};
    local r = 0;
    for i=0,n-1 do
        local pos = args[i+1];
        local vv = band(rshift(v,pos),1);
        r = bit.bor(r,lshift(vv,n-i-1));
    end
    return r;
end



local function unknown(address) return 0; end
local function cZero  (address) return 0; end
local function cOne   (address) return 1; end
local function bit_3  (address) return BIT(address,3); end
local function bit_4  (address) return BIT(address,4); end
local function bit_7  (address) return BIT(address,7); end
local function not_3  (address) return bxor(BIT(address,3),1); end
local function not_4  (address) return bxor(BIT(address,4),1); end
local function not_7  (address) return bxor(BIT(address,7),1); end
local function xor_37 (address) return bxor(BIT(address,3),BIT(address,7)); end
local function xnor_37(address) return bxor(bxor(BIT(address,3),BIT(address,7)),1); end
local function xor_47 (address) return bxor(BIT(address,4),BIT(address,7)); end
local function xnor_47(address) return bxor(bxor(BIT(address,4),BIT(address,7)),1); end
local function nor_34 (address) return bxor(bor(BIT(address,3),BIT(address,4)),1); end
local function impl_43(address)
    --return BIT(address,3)||(BIT(address,4)^1);
    local v = BIT(address,3);
    if(v~=0)then
        return 1;
    end
    v = BIT(address,4);
    v = bxor(v,1);
    if(v~=0)then
        return 1;
    end
    return 0;
end

local rot_enabling = {
	[0] = {[0] = bit_3  , not_3  , bit_3  , not_3  },
	{[0] = bit_3  , not_3  , bit_3  , not_3  },
	{[0] = bit_4  , bit_4  , bit_4  , bit_4  },
	{[0] = bit_4  , not_4  , bit_4  , not_4  },
	{[0] = bit_3  , bit_3  , bit_3  , bit_3  },
	{[0] = nor_34 , bit_7  , bit_7  , cZero  },
	{[0] = cZero  , cOne   , cZero  , cOne   },
	{[0] = impl_43, xor_37 , xnor_37, not_3  },
	{[0] = bit_3  , bit_3  , not_3  , not_3  },
	{[0] = bit_4  , bit_4  , not_4  , not_4  },
	{[0] = cZero  , cZero  , cZero  , cZero  },
	{[0] = nor_34 , bit_7  , not_7  , cOne   },
	{[0] = bit_3  , not_3  , bit_3  , not_3  },
	{[0] = cZero  , cOne   , cOne   , cZero  },
	{[0] = bit_4  , not_4  , bit_4  , not_4  },
	{[0] = unknown, unknown, unknown, unknown},
};

local rot_direction = {
    [0]={[0]=bit_3  , xor_37 , xnor_37, not_3  , bit_3  , xor_37 , xnor_37, not_3  },
	{[0]=cZero  , not_7  , not_7  , cZero  , cZero  , not_7  , not_7  , cZero  },
	{[0]=bit_4  , xor_47 , xnor_47, not_4  , bit_4  , xor_47 , xnor_47, not_4  },
	{[0]=bit_3  , not_7  , bit_7  , cZero  , cOne   , not_7  , bit_7  , cZero  },
}

local key = {
	[0] = 0x4A, 0x2B, 0xC1, 0x5F, 0xAB, 0x86, 0x8B, 0x7A, 0x5F, 0x4F, 0xE4, 0xFF,
	0x8B, 0x55, 0x26, 0x4D, 0x3A, 0xC1, 0xBE, 0xE3, 0x19, 0xF6, 0xF0, 0xC0,
	0x95, 0x70, 0xB2, 0x85, 0x0B, 0x32, 0xF4, 0x3C, 0x74, 0xDB, 0x2C, 0xAB,
	0x93, 0xBC, 0x0B, 0x4D, 0xE7, 0x6E, 0xAD, 0x05, 0xB0, 0xD2, 0x79, 0xE8,
	0x8A, 0x3B, 0xCC, 0x0C, 0x2E, 0x2C, 0x2F, 0xD4, 0x77, 0x63, 0xEF, 0x05,
	0x8D, 0xD1, 0xF5, 0xBF, 0x58, 0xAA, 0xF2, 0x03, 0xF7, 0x4C, 0x61, 0xD7,
	0xF0, 0x73, 0xF7, 0x24, 0xF7, 0x60, 0x55, 0xB1, 0x19, 0x12, 0xC8, 0x71,
	0xEA, 0xC4, 0x1D, 0xD5, 0xDD, 0x84, 0x30, 0xC0, 0x10, 0xE7, 0xC3, 0xC8,
	0x37, 0x5B, 0xB2, 0x93, 0xCD, 0x3D, 0xE5, 0xE1, 0x91, 0x43, 0xA0, 0x89,
	0x4B, 0x05, 0x0A, 0x54, 0x57, 0x86, 0x5A, 0xAA, 0x24, 0xB9, 0x12, 0x5B,
	0x18, 0x10, 0xDC, 0xDE, 0x42, 0x6D, 0xC9, 0xD0, 0x6A, 0xBC, 0x37, 0x14,
	0x34, 0xB0, 0xDC, 0x63, 0x24, 0x19, 0xC1, 0xCD, 0x5A, 0x4A, 0x34, 0x42,
	0xA3, 0x0E, 0xDC, 0x31, 0xFC, 0x0C, 0x05, 0xCC, 0x90, 0x3F, 0x9B, 0xC6,
	0xBD, 0xFB, 0x3C, 0x7D, 0x67, 0x36, 0x0A, 0x9E, 0xC6, 0x05, 0xAD, 0x26,
	0x68, 0x2B, 0x79, 0xD8, 0xBE, 0xEE, 0x6A, 0xFA, 0x67, 0xC3, 0x05, 0x9C,
	0xC9, 0x0D, 0xAE, 0xF2, 0xDD, 0x45, 0xB7, 0x01, 0x07, 0x09, 0xC8, 0x02,
	0x8F, 0xF0, 0x32, 0x09, 0xD2, 0x6C, 0x21, 0xB1, 0x94, 0xB3, 0x31, 0x51,
	0xFD, 0xBF, 0x45, 0xC5, 0x0A, 0xF5, 0xDB, 0xE4, 0x1D, 0xEC, 0x6A, 0xF4,
	0x07, 0x5D, 0x3D, 0xB7, 0xBA, 0xAE, 0x34, 0x04, 0x4A, 0x0C, 0xF4, 0x8C,
	0xF0, 0x75, 0xA3, 0x5C, 0xE2, 0xE6, 0xB1, 0xB1, 0x30, 0x03, 0xD8, 0x51,
	0xFB, 0x84, 0x9F, 0x40, 0x57, 0x69, 0xA1, 0x5B, 0xEE, 0xBD, 0x58, 0xCF,
	0x04, 0x64, 0x08, 0xF5
};


local group15 = {[0] = 15,11, 7, 5};  -- 15 is a guess
local group14 = {[0] = 14, 9, 3, 2};
local group13 = {[0] = 13,10, 6, 1};
local group12 = {[0] = 12, 8, 4, 0};

local function rot_enabled(address,group)
    local enabled = 0;
    for j=0,4-1 do
        if(BIT(address,8+group[j])~=0)then
            local aux = bxor(address,(0x1b*BIT(address,2)));
            enabled = rot_enabling[group[j]][band(aux,3)](aux);
            break;
        end
    end
    return enabled;
end

local function rot_group(address,group)
    local aux = rot_direction[band(group[0],3)][band(address,7)](address);
    return aux*2-1;
end

local function rol(num,shift)
    local r = band(lshift(num,shift),0xffff);
    local l = band(rshift(num,16-shift),0xffff);
    return band(bor(r,l),0xffff)
end

local  function rotation(address)
	-- rotation depending on all the address bits
	local enabled0 = rot_enabled(address, group15);
    local rot = (enabled0 * rot_group(address, group15) * 9);
    --print(enabled0,rot);

	local enabled1 = bxor(enabled0 , rot_enabled(address, group14));
    rot = rot + (enabled1 * rot_group(address, group14) * 1);
    --print(enabled0,rot);

	local enabled2 = bxor(enabled0 ,rot_enabled(address, group13));
    rot = rot + (enabled2 * rot_group(address, group13) * 2);
    --print(enabled0,rot);

	local enabled3 = bxor(enabled0 , rot_enabled(address, group12));
    rot = rot + (enabled3 * rot_group(address, group12) * 4);
    --print(enabled0,rot);

	-- block-independent rotation (just depending on the lowest 8 bits)
	local rot2  = 4*BIT(address,0);
    rot2 = rot2 +
            1*BIT(address,4)*
            (BIT(address,0)*2-1);


    rot2 = rot2 +
            4*BIT(address,3)
            *(BIT(address,0)*2-1);

    rot2 = rot2 * (
                bor(BIT(address,7),bxor(bxor(BIT(address,0)),BIT(address,1),1))
                *2-1);

    rot2 = rot2 + (
        2*
        band(bxor(BIT(address,0),BIT(address,1)),bxor(BIT(address,7),1))
    );

    return band(rot+rot2,0xf);
end

local idx = 0;
function deobfuscate(cipherword,word_address)
    local shift = rotation(word_address);
    print(string.format("s:%X",shift));
    local aux = rol(cipherword,shift);
    --
    print(string.format("a:%X",aux));
    aux = bitswap(aux,16,10,9,8,7,0,15,6,5,   14,13,4,3,12,11,2,1);
    print(string.format("a:%X",aux));
    print('-------------')
    return aux;
end

local triggers = {
    [0] = {[0] =0x000101, 0x000001}, {[0] =0x000802, 0x000800}, {[0] =0x000204, 0x000004}, {[0] =0x000408, 0x000408},
	{[0] =0x010010, 0x000010}, {[0] =0x020020, 0x000020}, {[0] =0x040040, 0x000040}, {[0] =0x080080, 0x080080},
	{[0] =0x100100, 0x000100}, {[0] =0x200200, 0x200000}, {[0] =0x400400, 0x400000}, {[0] =0x800801, 0x000001},
	{[0] =0x001004, 0x001000}, {[0] =0x002010, 0x002000}, {[0] =0x004040, 0x000040}, {[0] =0x008100, 0x008100}
}

function decrypt(cipherword,word_address)
    --key-independent manipulation
    local aux = deobfuscate(cipherword,word_address);

    --key-dependent manipulation
    for i=0,16-1 do
        local k;
        local ii = i;
        if(band(word_address,triggers[i][0])==triggers[i][1])then
            local k = key[band(word_address,0xff)];
            aux = bxor(aux,
                lshift(
                    BIT(k,bit.band(i,7)),
                i)
            );
            --k = key[band(word_address,0xff)];
            --ii = band(7,i);
        else
            aux = bxor(aux,
            lshift(BIT(0x1a3a,i),i)
            );
            --k = 0x1a3a;
        end

        --aux = bxor(aux,lshift(BIT(k,i),ii));
    end
    return aux;
end

function decrypter_rom(rom,size,offset)
    local rom = ffi.cast('uint16_t*',rom);
    local idx = 0;
    for i=0,size/2-1 do
        rom[i] = decrypt(rom[i],i + offset);
        print(string.format('%X',rom[i]))
        -- if(i==1 and rom[i]~=0xe92d)then
        --     break;
        -- end
        -- idx = idx + 1;
        -- if(idx>3)then
        --     break;
        -- end
    end
end

assert(#arg>0,"no file");
assert(#arg>0,"no file");
local file = io.open(arg[1],"rb");
assert(file,"file open error")
local data = file:read("*a");
local rom = ffi.new('uint8_t[?]',#data);
ffi.copy(rom,data);
--print(rom[1]);
--print(ffi.sizeof(rom));

--mslug3_en_68k(rom,#data);
decrypter_rom(rom,#data,0);
local out = arg[1]..'.d';
local file = io.open(out,"wb");
file:write(ffi.string(rom,#data));
file:close();


